Swift TOO API
Swift MOC

swift_too module

Swift_Clock example - handling corrections to Swift's reported times

API version = 1.2, swifttools = 2.4

Author: Jamie A. Kennea (Penn State)

from swifttools.swift_too import Clock, ObsQuery, VisQuery
from datetime import datetime,timedelta

Introduction to Clock Correction

Swift has it's own built in clock, and it is this clock that timestamps all Swift events, including telemetry points, X-ray photon event times, the start and stop of observations and the like. This clock measures time in what's know as Mission Elapse Time (MET). This is defined simply as the number of seconds since January 1st, 2001, as measured by Swift's clock.

In addition, we have a time system known as "Swift Time", this is simply a conversion of MET into a standard UT time format, so for example, for MET = 600000000s, 600 million seconds after Jan 1st, 2001, let's calculate what that would be in "Swift Time".

met = 600000000

print(f"MET {met} = {datetime(2001,1,1) + timedelta(seconds=met)}")
MET 600000000 = 2020-01-06 10:40:00

As you can see, 600 million seconds is approximately 19 years.

"Swift Time" is approximately UTC, however, there are several issues that mean that it is not actually UTC.

Firstly, it does not include any leap seconds. There have been 5 leap seconds, most recently at the end of 2016 (as of writing), added to the UTC scale since the epoch of MET, Jan 1st, 2001. You can find out information about leap seconds at this NIST page.

Secondly, and more crucially, as Swift's clock is fully internal, and not corrected by GPS and otherwise, and it unfortunately is drifting over time. At Swift entered it's 17th year of operations in November 2021, this drift although small has become significant, and needs to be corrected for if you wish to obtain times that accurately match UT time.

For this reason we have the concept of the Universal Time Correction Factor (UTCF). This is a floating point value that when added to Swift Time, corrects it to UTC. The UTCF includes both a correction for leap seconds, and the clock drift. As of Jan 1st, 2022, the utcf value is -28.3567 seconds, including 5 leap seconds. Therefore to correct Swift Time to UTC, on Jan 1st, 2022, add this value.

However, this clock correction changes non-linearly with time, therefore the correction needs to be calculated for each time being corrected.

The Swift_Clock class

The Swift_Clock class is designed to convert the three time formats: MET (met), Swift Time (swifttime) and UTC Time (utctime), by applying the UTCF (utcf). The class is fairly simple, simply give one of the three time formats, and it will return back the other two, and the UTCF value. Here we demonstrate it's usage:

cc = Clock(swifttime='2022-01-01 00:00:00')
cc
MET (s) Swift Time (default) UTC Time UTCF (s)
662688000.0 2022-01-01 00:00:00 2021-12-31 23:59:31.643287 -28.356713

So we can see by giving a single time, here in the Swift Time format, the Swift_Clock class returns 4 values, met the Mission elapsed time in seconds, the echoed back Swift Time, UTC time, and UTCF. First let's take a look at the UTC time, here represented by the utctime attribute.

print(cc.utctime)
2021-12-31 23:59:31.643287

So this is a representation of the actual UTC time when the Swift clock clicked over to 2022. Think of it this way, Swift set off the fireworks to celebrate the New Year 28.3567 seconds to early, because it's clock is fast by 23.3567 seconds, and it doesn't know anything about the 5 leap seconds since the start of 2001. Note that utctime is a datetime instance. To keep things simple, it is a "naive datetime", i.e. it does not have a timezone attached.

A note about the datetime module

A note about the Python datetime module. It does not support leap seconds, therefore although it's useful to use it to represent UTC time with, as it provides an easy container format, if you use it to do arithmatic between dates, it will not give the correct value.

Let's demonstrate this by looking at how datetime handles a know leap second, which occurred at the end of 2016.

dt1 = datetime(2016,12,31,23,59,59)
dt2 = datetime(2017,1,1,0,0,0)
dttd = dt2 - dt1
print(f"datetime time difference = {dttd.total_seconds():.1f}s")
datetime time difference = 1.0s

So according to datetime, between 23:59:59 and midnight, there was one second, but we know there was a leap second here.

If you want to handle these leap seconds correctly, I would recommend using astropy Time module. Let's see what happens when we use this. Thankfully Time can easily convert datetime values into UTC values. Let's do the above again, but using Time.

from astropy.time import Time
import astropy.units as u

at1 = Time(dt1,format='datetime',scale='utc')
at2 = Time(dt2,format='datetime',scale='utc')
atd = at2 - at1
print(f"Time difference = {atd.to_value(u.s):.1f}s")
Time difference = 2.0s

The value here is 2 seconds because there was a leap second at the end of 2016, meaning we here was a time of 23:59:60 that was the actual last second of 2016. astropy handles this, but datetime does not. Therefore if strict accuracy is needed for calculations over large time periods, I would recommend converting to astropy Time.

Handling large numbers of times

Converting single values is useful, for example, if you know a GRB went off a certain time, and you have BAT data you wish to compare too, BAT events are tagged with MET, Swift_Clock allows for easy conversion of a UTC time to a MET.

However, in many cases you may wish to correct a larger range of numbers. This is simple to do, you can simply pass them as alist or tuple. For example:

cc = Clock()
cc.utctime = '2022-01-01 00:00:00', '2021-01-01 00:00:00', '2020-01-01 00:00:00'
cc.submit()
cc
MET (s) Swift Time UTC Time (default) UTCF (s)
662688028.356715 2022-01-01 00:00:28.356715 2022-01-01 00:00:00 -28.356715
631152026.251282 2021-01-01 00:00:26.251282 2021-01-01 00:00:00 -26.251282
599529624.18474 2020-01-01 00:00:24.184740 2020-01-01 00:00:00 -24.18474

Here you see that the UTCF values are different for each time, as the three times given are a year apart. Note that the drift seems to increasing by approximately 2.1 seconds every year.

You can access the arrays of METs, Swift Times and UTC times using the met, swifttime and utctime parameters as expected, and they will return a list:

cc.met
[662688028.356715, 631152026.251282, 599529624.18474]

However, you can also access times directly as follows:

cc[0]
MET (s) Swift Time UTC Time (default) UTCF (s)
662688028.356715 2022-01-01 00:00:28.356715 2022-01-01 00:00:00 -28.356715

Introducing swiftdatetime

cc[0] returns swiftdatetime object. swiftdatetime is an extended version of the standard python datetime class. It is extended inso far that it has new attributes, met, utctime, swifttime and isutc.

isutc determines what time system it is defined in. Above you see that it says "UTC Time (default)" which means that isutc = True. If we print it, it will give us the UTC time by default:

print(cc[0])
2022-01-01 00:00:00

swiftdatetime objects work exactly like datetime objects, you can add to them, subtract them from one another, and use standard methods to create them. The following are some examples. Firstly if we subtract one from another, we get a timedelta object.

print(f"{cc[0]} - {cc[1]} = {cc[0]-cc[1]}")
2022-01-01 00:00:00 - 2021-01-01 00:00:00 = 365 days, 0:00:00

Note that we can add time to swiftdatetime instances also. But it's important to note that the resultant value will not include a UTCF value. This is because UTCF changes non-linearly with time, so cannot be propogated. So the end result will be a swiftdatetime in time system what's being added to:

one_week_after = cc[0] + timedelta(days=7)
one_week_after
MET (s) Swift Time UTC Time (default) UTCF (s)
None None 2022-01-08 00:00:00 None

Note that only utctime is set for this new value.

If we wish to obtain UTCF and therefore Swift Time and MET for this result, we will need to look up the UTCF with Swift_Clock.

Clock(utctime=one_week_after)
MET (s) Swift Time UTC Time (default) UTCF (s)
663292828.397507 2022-01-08 00:00:28.397507 2022-01-08 00:00:00 -28.397507

Not that here, as one_week_after is in the UTC time system, we need to assign it to utctime. If we tried to assign it to swifttime it would cause an error, because one_week_after.swifttime == None.

Universal Clock Correction

Using the new clock_correct method, any class that returns times as a result can have those values clock corrected. Let's demonstrate that with an ObsQuery.

obs = ObsQuery(name='RR Lyrae')
obs
Begin Time End Time Target Name Observation Number Exposure (s) Slewtime (s)
2013-01-18 14:22:02 2013-01-18 14:27:01 Kepler_Reg3_Pt03 00048634001 235 64
2013-01-31 22:32:34 2013-01-31 22:45:58 Kepler_Reg3_Pt03 00048634002 650 154
2013-02-06 08:30:02 2013-02-06 08:39:58 Kepler_Reg3_Pt03 00048634003 510 86
2013-02-06 14:44:02 2013-02-06 14:50:57 Kepler_Reg3_Pt03 00048634003 255 160
2013-02-06 19:46:02 2013-02-06 19:53:01 Kepler_Reg3_Pt03 00048634003 230 189

Here the 5 Swift observations of RR Lyrae, have begin and end times associated with them, if we look at those times now, we will find that they are now swiftdatetime instances, which like the output of Swift_Clock, contain information about MET, UTC Time and UTCF as well as Swift Time.

obs[0].begin
datetime.datetime(2013, 1, 18, 14, 22, 2)

However, in this case you see that the default time is Swift Time, and there is no UTCF defined. Therefore we do not know what the UTC time for the start of the first observation is. However, there is a new method to add these corrections:

obs.clock_correct()
obs
Begin Time (UTC) End Time (UTC) Target Name Observation Number Exposure (s) Slewtime (s)
2013-01-18 14:21:51.884888 2013-01-18 14:26:50.884875 Kepler_Reg3_Pt03 00048634001 234 63
2013-01-31 22:32:23.834881 2013-01-31 22:45:47.834847 Kepler_Reg3_Pt03 00048634002 649 153
2013-02-06 08:29:51.814584 2013-02-06 08:39:47.814558 Kepler_Reg3_Pt03 00048634003 509 85
2013-02-06 14:43:51.813610 2013-02-06 14:50:46.813592 Kepler_Reg3_Pt03 00048634003 254 159
2013-02-06 19:45:51.812824 2013-02-06 19:52:50.812806 Kepler_Reg3_Pt03 00048634003 229 188

Note that the table the times have now changed, and are labelled as being in UTC time. However, the information about Swift Time, UTCF and MET are retained, as we can see if we look at one of time times individually.

obs[0].begin
MET (s) Swift Time UTC Time (default) UTCF (s)
380211722.0 2013-01-18 14:22:02 2013-01-18 14:21:51.884888 -10.115112

So, if you want to switch the default time for back Swift Time, you can easily do this. In fact you can swap between UTC and Swift Time using the to_utctime() and to_swifttime() methods. So if we want to convert all of the time in our ObsQuery to back to Swift Time for display as a table, just do the following:

obs.to_swifttime()
obs
Begin Time (Swift) End Time (Swift) Target Name Observation Number Exposure (s) Slewtime (s)
2013-01-18 14:22:02 2013-01-18 14:27:01 Kepler_Reg3_Pt03 00048634001 235 64
2013-01-31 22:32:34 2013-01-31 22:45:58 Kepler_Reg3_Pt03 00048634002 650 154
2013-02-06 08:30:02 2013-02-06 08:39:58 Kepler_Reg3_Pt03 00048634003 510 86
2013-02-06 14:44:02 2013-02-06 14:50:57 Kepler_Reg3_Pt03 00048634003 255 160
2013-02-06 19:46:02 2013-02-06 19:53:01 Kepler_Reg3_Pt03 00048634003 230 189

So now the observation table times are displayed as UTC times, rather than Swift Times.

Default Time System

So as default, anything relating to looking at Swift prior observations and Swift Planned observations are returned in the Swift time system. Therefore the default time system that Swift_ObsQuery and Swift_PlanQuery work in is Swift Time.

For Swift_VisQuery which queries when targets are visibile to Swift, as this information is calculated in UTC time system by default. Let's see this in action, by performing a Swift_VisQuery.

vis = VisQuery(266,-29,hires=True,length=1)
vis
Begin Time End Time Window length
2022-03-28 19:58:00 2022-03-28 20:26:00 0:28:00
2022-03-28 21:23:00 2022-03-28 22:02:00 0:39:00
2022-03-28 22:58:00 2022-03-28 23:37:00 0:39:00
2022-03-29 00:34:00 2022-03-29 01:13:00 0:39:00
2022-03-29 02:09:00 2022-03-29 02:48:00 0:39:00
2022-03-29 03:45:00 2022-03-29 04:24:00 0:39:00
2022-03-29 05:21:00 2022-03-29 05:59:00 0:38:00
2022-03-29 06:56:00 2022-03-29 07:35:00 0:39:00
2022-03-29 08:32:00 2022-03-29 09:11:00 0:39:00
2022-03-29 10:07:00 2022-03-29 10:46:00 0:39:00
2022-03-29 11:43:00 2022-03-29 12:15:00 0:32:00
2022-03-29 13:18:00 2022-03-29 13:54:00 0:36:00
2022-03-29 14:54:00 2022-03-29 15:32:00 0:38:00
2022-03-29 16:29:00 2022-03-29 17:08:00 0:39:00
2022-03-29 18:05:00 2022-03-29 18:44:00 0:39:00
2022-03-29 19:41:00 2022-03-29 19:58:00 0:17:00

OK now let's perform a clock correction on this.

vis.clock_correct()
vis
Begin Time (UTC) End Time (UTC) Window length
2022-03-28 19:58:00 2022-03-28 20:26:00 0:28:00
2022-03-28 21:23:00 2022-03-28 22:02:00 0:39:00
2022-03-28 22:58:00 2022-03-28 23:37:00 0:39:00
2022-03-29 00:34:00 2022-03-29 01:13:00 0:39:00
2022-03-29 02:09:00 2022-03-29 02:48:00 0:39:00
2022-03-29 03:45:00 2022-03-29 04:24:00 0:39:00
2022-03-29 05:21:00 2022-03-29 05:59:00 0:38:00
2022-03-29 06:56:00 2022-03-29 07:35:00 0:39:00
2022-03-29 08:32:00 2022-03-29 09:11:00 0:39:00
2022-03-29 10:07:00 2022-03-29 10:46:00 0:39:00
2022-03-29 11:43:00 2022-03-29 12:15:00 0:32:00
2022-03-29 13:18:00 2022-03-29 13:54:00 0:36:00
2022-03-29 14:54:00 2022-03-29 15:32:00 0:38:00
2022-03-29 16:28:59.999999 2022-03-29 17:08:00 0:39:00.000001
2022-03-29 18:05:00 2022-03-29 18:44:00 0:39:00
2022-03-29 19:41:00 2022-03-29 19:58:00 0:17:00

OK so now we see that the Begin and End times are labelled as UTC, but they are unchanged from before as they were always calculated in UTC. This is because the API knows that VisQuery operates in UTC time system. You can of course Switch to Swift times using to_swifttime() method.

vis.to_swifttime()
vis
Begin Time (Swift) End Time (Swift) Window length
2022-03-28 19:58:28.863409 2022-03-28 20:26:28.863523 0:28:00.000114
2022-03-28 21:23:28.863754 2022-03-28 22:02:28.863912 0:39:00.000158
2022-03-28 22:58:28.864140 2022-03-28 23:37:28.864298 0:39:00.000158
2022-03-29 00:34:28.864529 2022-03-29 01:13:28.864687 0:39:00.000158
2022-03-29 02:09:28.864914 2022-03-29 02:48:28.865072 0:39:00.000158
2022-03-29 03:45:28.865304 2022-03-29 04:24:28.865462 0:39:00.000158
2022-03-29 05:21:28.865693 2022-03-29 05:59:28.865847 0:38:00.000154
2022-03-29 06:56:28.866078 2022-03-29 07:35:28.866237 0:39:00.000159
2022-03-29 08:32:28.866468 2022-03-29 09:11:28.866626 0:39:00.000158
2022-03-29 10:07:28.866853 2022-03-29 10:46:28.867011 0:39:00.000158
2022-03-29 11:43:28.867242 2022-03-29 12:15:28.867372 0:32:00.000130
2022-03-29 13:18:28.867628 2022-03-29 13:54:28.867774 0:36:00.000146
2022-03-29 14:54:28.868017 2022-03-29 15:32:28.868171 0:38:00.000154
2022-03-29 16:29:28.868402 2022-03-29 17:08:28.868561 0:39:00.000159
2022-03-29 18:05:28.868792 2022-03-29 18:44:28.868950 0:39:00.000158
2022-03-29 19:41:28.869181 2022-03-29 19:58:28.869250 0:17:00.000069

Swift Mission Operations Center

The Pennsylvania State University
301 Science Park Road,
Building 2 Suite 332,
State College, PA 16801
USA
☎ +1 (814) 865-6834
📧 swiftods@swift.psu.edu

Swift MOC Team Leads

Mission Director: John Nousek
Science Operations: Jamie Kennea
Flight Operations: Mark Hilliard
UVOT: Michael Siegel
XRT: Jamie Kennea